﻿/* --------------------------------------------------------------
 * exMath (C) Laurens Ruijtenberg
 * 
 * This code is being distributed under the MS-PL license. Feel
 * free to contact me if you like this code.
 * -------------------------------------------------------------- */

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using exMath.Calculus;


namespace exMath.Common
{
    //delegate definitions for mathematical functions.
    public delegate double MathFunction(double x);
    public delegate double MathFunction2(double x, double y);
    public delegate double MathFunction3(double x, double y, double z);

    public class Functions
    {
        /// <summary>
        /// Applies the specified mathematical Function to each interval within the specified domain.
        /// </summary>
        /// <param name="DomainStart"></param>
        /// <param name="DomainEnd"></param>
        /// <param name="Interval"></param>
        /// <param name="Function"></param>
        /// <returns></returns>
        public static double[] ExecuteFunction(double DomainStart, double DomainEnd, double Interval, MathFunction Function)
        {
            double[] DomainValues = ToDomainIntervalArray(DomainStart, DomainEnd, Interval);
            return ExecuteFunction(DomainValues, Function);
        }

        /// <summary>
        /// Executes the specified mathematical Function to each specified value.
        /// </summary>
        /// <param name="Values">Domain values that need to be used as input for function.</param>
        /// <param name="Function">Function to execute.</param>
        /// <returns></returns>
        public static double[] ExecuteFunction(double[] Values, MathFunction Function)
        {
            double[] Results = new double[Values.Length];
            
            //intialize local result-cache from GFC.
            MethodInfo mInfo = Function.Method;
            string FunctionID = mInfo.DeclaringType.Name + "_" + mInfo.Name;
            FunctionCache Cache = GFC.GetCacheItem(FunctionID);
            
            //Execute functions (todo: add multithreading support).
            for (int i = 0; i < Values.Length; i++)
            {
                if (GFC.PerformCaching)
                {
                    if (Cache.ResultExists(Values[i]))
                    {
                        Results[i] = Function(Values[i]);
                        Cache[Values[i]] = Results[i];
                    }
                    else
                    {
                        Results[i] = Cache[Values[i]];
                    }
                }
                else
                {
                    Results[i] = Function(Values[i]);
                }
            }

            //write local result-cache back to GFC.
            GFC.SetCacheItem(FunctionID, Cache);

            return Results;
        }

        /// <summary>
        /// Executes a function which accepts two domain values as an input.
        /// </summary>
        /// <param name="ValuesX"></param>
        /// <param name="ValuesY"></param>
        /// <param name="Function"></param>
        /// <returns></returns>
        public static double[] ExecuteFunction(double[] ValuesX, double[] ValuesY, MathFunction2 Function)
        {
            if (ValuesX.Length != ValuesY.Length)
                throw new Exception("Parameter amount must be equal in both dimensions.");

            double[] Results = new double[ValuesX.Length];

            for (int i = 0; i < ValuesX.Length; i++)
            {
                Results[i] = Function(ValuesX[i], ValuesY[i]);
            }

            return Results;
        }

        /// <summary>
        /// Generates an array of domain values from DomainBegin to DomainEnd, at each Interval.
        /// </summary>
        /// <param name="DomainBegin"></param>
        /// <param name="DomainEnd"></param>
        /// <param name="Interval"></param>
        /// <returns></returns>
        public static double[] ToDomainIntervalArray(double DomainBegin, double DomainEnd, double Interval)
        {
            double[] DomainValues = new double[(int)((DomainEnd - DomainBegin) / Interval) + 1];
            double CurrentDomainValue = DomainBegin;
            int counter = 0;

            while (CurrentDomainValue <= DomainEnd)
            {
                DomainValues[counter] = CurrentDomainValue;
                CurrentDomainValue += Interval;
                counter++;
            }

            return DomainValues;
        }
    }
}